package com._21cn.framework.utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com._21cn.framework.entity.JsonObject;

/**
 * 将任何1个实体对象转换为json格式的字符串，依赖于org.apache.commons.beanutils包。其中Date型的属性将按指定的或默认的日期格式转换位字符串
 *
 */
public class JsonEncoder {

	private static final String lineSeparator;
	private static JsonEncoder instance = new JsonEncoder();

    static {
        String ls = System.getProperty("line.separator"); //$NON-NLS-1$
        if (ls == null) {
            ls = "\n"; //$NON-NLS-1$
        }
        lineSeparator = ls;
    }
    
    public static JsonEncoder getInstance(){
    	return instance;
    }
    
	/**
	 * 将obj编码为json格式
	 * @param obj
	 * @return json格式的字符串
	 */
	public static String encode( Object obj ){
		return encode( obj, new ArrayList<Object>(), null, null, false, 1 );
	}
	
	/**
	 * 将obj编码为json格式，只输出部分属性
	 * @param obj
	 * @param propertyNameSet 需要输出的部分属性名称的集合
	 * @return json格式的字符串
	 */
	public static String encode( Object obj, Set<String> propertyNameSet ){
		return encode( obj, new ArrayList<Object>(), null, propertyNameSet, false, 1 );
	}

	public static String encode( Object obj, String[] propertyNames ){
		return encode( obj, new ArrayList<Object>(), null, getPropertyNameSet(propertyNames), false, 1 );
	}

	/**
	 * 将obj编码为json格式，只输出部分属性
	 * @param obj
	 * @param excludeNameSet 不需要输出的部分属性名称的集合
	 * @return json格式的字符串
	 */
	public static String encodeExclude( Object obj, Set<String> excludeNameSet ){
		return encode( obj, new ArrayList<Object>(), null, excludeNameSet, true, 1 );
	}

	public static String encodeExclude( Object obj, String[] excludeNames ){
		return encode( obj, new ArrayList<Object>(), null, getPropertyNameSet(excludeNames), true, 1 );
	}

	/**
	 * 将obj编码为json格式，指定日期格式
	 * @param obj
	 * @param dateFormat 指定日期格式
	 * @return json格式的字符串
	 */
	public static String encode( Object obj, String dateFormat ){
		return encode(  obj, new ArrayList<Object>(),  dateFormat, null, false, 1 );
	}
	
	/**
	 * 将obj编码为json格式，只输出部分属性
	 * @param obj
	 * @param propertyNameSet 需要输出的部分属性名称的集合
	 * @param dateFormat 指定日期格式
	 * @return json格式的字符串
	 */
	public static String encode( Object obj, String dateFormat, Set<String> propertyNameSet ){
		return encode( obj, new ArrayList<Object>(), dateFormat, propertyNameSet, false, 1 );
	}

	public static String encode( Object obj, String dateFormat, String[] propertyNames ){
		return encode( obj, new ArrayList<Object>(), dateFormat, getPropertyNameSet(propertyNames), false, 1 );
	}

	/**
	 * 将obj编码为json格式，只输出部分属性
	 * @param obj
	 * @param excludeNameSet 不需要输出的部分属性名称的集合
	 * @param dateFormat 指定日期格式
	 * @return json格式的字符串
	 */
	public static String encodeExclude( Object obj, String dateFormat, Set<String> excludeNameSet ){
		return encode( obj, new ArrayList<Object>(), dateFormat, excludeNameSet, true, 1 );
	}
	
	public static String encodeExclude( Object obj, String dateFormat, String[] excludeNames ){
		return encode( obj, new ArrayList<Object>(), dateFormat, getPropertyNameSet(excludeNames), true, 1 );
	}

	/**
	 * 将逗号隔开的多个属性名称分割为属性名的set集合
	 * @param propertyNames
	 * @return
	 */
	private static Set<String> getPropertyNameSet( String... propertyNames ){
		Set<String> propSet = new HashSet<String>();
		propSet.addAll( Arrays.asList( propertyNames ) );
		return propSet;
	}

	@SuppressWarnings(  "rawtypes" )
    private static String encode( Object obj, Collection<Object> cached, String dateFormat, Set<String> keySet, boolean exclude, int indentLevel ){
		
		if( obj == null ){
			return  "null";
		}
		
		if( obj instanceof String || obj instanceof Character){
			return encodeStringClosed( (String)obj ) ;
		}
		
		if( obj instanceof Number  || obj instanceof Boolean ){
			return obj.toString();
		}
		// 防止在属性递归序列化中，一个对象被序列化多次, 避免死循环
		for( Object cachedObj : cached ) {
			if( cachedObj == obj || cachedObj.equals( obj ) ) {
				return  "null";
			}
		}
		cached.add( obj );
		try{
			if( obj instanceof JsonObject ){
				return ((JsonObject)obj).toJsonString() ;
			}
			
			if( obj instanceof Date ){
				return encodeDate( (Date)obj, dateFormat ) ;
			}
			
			if( obj instanceof Map ){
				return encodeMap( (Map)obj, cached, dateFormat, keySet, exclude, indentLevel );
			}
			if( obj instanceof Collection ){
				return encodeCollection( (Collection)obj, cached, dateFormat,  keySet, exclude, indentLevel );
			}
			
			if( obj.getClass().isArray() ){
				if( obj instanceof byte[] )
					return Arrays.toString( (byte[])obj );
				if( obj instanceof short[] )
					return Arrays.toString( (short[])obj );
				if( obj instanceof int[] )
					return Arrays.toString( (int[])obj );
				if( obj instanceof long[] )
					return Arrays.toString( (long[])obj );
				if( obj instanceof float[] )
					return Arrays.toString( (float[])obj );
				if( obj instanceof double[] )
					return Arrays.toString( (double[])obj );
				if( obj instanceof boolean[] )
					return Arrays.toString( (boolean[])obj );
				return encodeArray( (Object[])obj, cached, dateFormat, keySet, exclude, indentLevel );
			}
			Map prop = getPropertyMap(obj, keySet, exclude );
			if( prop.size() > 0 )
				return encodeMap( prop, cached, dateFormat, keySet, exclude, indentLevel );
			
			return encodeStringClosed( obj.toString() );
		}
		finally{
			cached.remove( obj );
		}
	}

	@SuppressWarnings( "rawtypes" )
	private static Map getPropertyMap(Object obj, Set<String> keySet, boolean exclude ){
		Map result =  ReflectUtil.getPropertyMap( obj, keySet, exclude );
		result.remove( "jsonString" );
		return result;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
    private static String encodeMap( Map obj, Collection<Object> cached, String dateFormat, Set<String> keySet, boolean exclude, int indentLevel ){
		if( obj == null )
			return "null";
		StringBuilder result = new StringBuilder("{");
		
		Map prop = getPropertyMap(obj, keySet, exclude);
		prop.remove( "empty" );
		prop.remove( "firstEntry" );
		prop.remove( "lastEntry" );

		if( prop.size() == 0 )
			prop = obj;
		else {
			for( Iterator iter = obj.entrySet().iterator(); iter.hasNext(); ){
				Map.Entry entry = (Map.Entry)iter.next();
				prop.put( entry.getKey().toString(), entry.getValue() );
			}
		}

		boolean and = false;
		for( Iterator iter = prop.entrySet().iterator(); iter.hasNext(); ){
			Map.Entry entry = (Map.Entry)iter.next();
			if( entry.getKey().equals("jsonString") ){
				return encodeString( (String)prop.get("jsonString") );
			}
			if( and )
				result.append(",");
			else
				and = true;
            /*if( result.length()/lineWidth > line ){
        		line = result.length()/lineWidth;
        		indent( result, indentLevel );
        	}*/
        	indent( result, indentLevel );
			result.append( encodeStringClosed( entry.getKey().toString() ) ).append(":")
				.append( encode( entry.getValue(), cached, dateFormat, keySet, exclude, indentLevel+1 ) );
		}
		if( and )
			indent( result, indentLevel-1 );
		return result.append("}").toString();
	}

	private static void indent(StringBuilder sb, int indentLevel) {
        sb.append(lineSeparator);
        for (int i = 0; i < indentLevel; i++) {
            sb.append("\t"); 
        }
    }
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
    private static String encodeCollection( Collection obj, Collection<Object> cached, String dateFormat, Set<String> keySet, boolean exclude, int indentLevel ){
		Map prop = getPropertyMap(obj, keySet, exclude);
		prop.remove( "empty" );
		
		if( prop.size() == 0 )
			return encodeArray( obj.toArray(), cached, dateFormat,  keySet, exclude, indentLevel );
		
		prop.put( "list", obj.toArray() );
		return encodeMap( prop, cached, dateFormat, keySet, exclude, indentLevel );
	}

	private static String encodeArray( Object[] obj, Collection<Object> cached, String dateFormat, Set<String> keySet, boolean exclude, int indentLevel ){
		StringBuilder result = new StringBuilder("[");
		boolean and = false;
		for( int i=0; i< obj.length; i++ ){
			if( and )
				result.append(",");
			else
				and = true;
           /* if( result.length()/lineWidth > line ){
            	line = result.length()/lineWidth;
            	indent( result, indentLevel );
            }*/
        	indent( result, indentLevel );
			result.append( encode( obj[i], cached, dateFormat,  keySet, exclude, indentLevel+1 ) );
		}
		if( and )
			indent( result, indentLevel-1 );
		return result.append("]").toString();
	}
	
	private static String encodeDate( Date obj, String format ){
		if( "simpleFormat".equalsIgnoreCase( format ) )
			return encodeStringClosed( DateUtil.formatDate( obj ));
		return encodeStringClosed( DateUtil.formatDate( obj, format ) );
	}

	private static String encodeStringClosed( String obj ){
		if( obj == null ) return  "null";
		
		StringBuilder result = new StringBuilder();
		
		result.append('"');
	    int length = obj.length();
	    for (int i = 0; i < length; i++) {
	      char c = obj.charAt(i);
	      switch (c) {
	      case '"':
	    	  result.append('\\').append(c);
	    	  break;
	      case '\\': //HEX:5C
	    	  if( i+1<length ){ //在iso编码状态时，5C有可能是某个中文字符的一部分
	    		  char n = obj.charAt( i+1 );
	    		  if(n!='b'||n!='f'||n!='n'||n!='r'||n!='t'||n!='\''||n!='"'||n!='\\'){
	    			  result.append(c);
	    			  break;
	    		  }
	    	  }
	    	  result.append('\\').append(c);
	    	  break;
	      case '\b':
	    	  result.append("\\b");
	    	  break;
	      case '\f':
	    	  result.append("\\f");
	    	  break;
	      case '\n':
	    	  result.append("\\n");
	    	  break;
	      case '\r':
	    	  result.append("\\r");
	    	  break;
	      case '\t':
	    	  result.append("\\t");
	    	  break;
	      default:
	    	  result.append(c);
	      }
	    }
	    result.append('"');
		return result.toString();
	}
	
	private static String encodeString( String obj ){
		if( obj == null ) return  "null";
		return  obj.replace("\n", "").replace("\r", "");
	}
	
}
